﻿using System;
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using ULSDeobfuscator;

namespace Elca.MvvmHelpers {
    public class TimelineDrawing : FrameworkElement, INotifyPropertyChanged {

        public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
                "ItemsSource",
                typeof(IList),
                typeof(TimelineDrawing));

        /// <summary>
        /// This property is used as a 'signal' to this element that the drawing should be re-rendered, because the underlying data has changed
        /// </summary>
        public static readonly DependencyProperty ItemsWidthProperty = DependencyProperty.Register(
                "ItemsWidth",
                typeof(long),
                typeof(TimelineDrawing),
                new FrameworkPropertyMetadata(0L, FrameworkPropertyMetadataOptions.AffectsRender, OnItemsWidthChanged));

        public IList ItemsSource {
            get {
                return (IList) GetValue(ItemsSourceProperty);
            }
            set {
                SetValue(ItemsSourceProperty, value);
            }
        }

        public long ItemsWidth {
            get {
                return (long) GetValue(ItemsWidthProperty);
            }
            set {
                SetValue(ItemsWidthProperty, value);
            }
        }

        private bool _needsToNotifyThatToolTipChanged;

        public string ToolTipText {
            get {

                _needsToNotifyThatToolTipChanged = true;

                if (ItemsSource == null) {
                    return String.Empty;
                }

                Point pt = Mouse.GetPosition(this);

                int index = FindTimelineElement((long) pt.X);

                //DebugLog("Asked for TooltipText at point = {0}, found index = {1}", pt.X, index);

                if (index >= ItemsSource.Count || index < 0) {
                    return String.Empty;
                }

                TimelineInfo ti = (TimelineInfo) ItemsSource[index];
                return ti.Explanation;
            }
        }

        public TimelineDrawing() {
            
            Binding tooltipBinding = new Binding("ToolTipText");
            tooltipBinding.RelativeSource = new RelativeSource(RelativeSourceMode.Self);

            SetBinding(ToolTipProperty, tooltipBinding);

            MouseMove += (sender, args) => {
                             if (_needsToNotifyThatToolTipChanged) {
                                 _needsToNotifyThatToolTipChanged = false;
                                 OnPropertyChanged("ToolTipText");
                             }
                         };
        }

        private int FindTimelineElement(long x) {
            
            if (ItemsSource == null || ItemsSource.Count == 0) {
                return -1;
            }

            int low = 0;
            int high = ItemsSource.Count - 1;

            while (low != high) {

                int index = low + (high - low) / 2;
                TimelineInfo ti = (TimelineInfo) ItemsSource[index];

                if (ti.CumulatedWidth == x) {
                    return index;
                }
                if (ti.CumulatedWidth > x) {
                    // the item at this index is to the right of point x => lower the upper limit
                    high = index;
                } else {
                    // the item at this index is to the left of x
                    // but is the next element to the right of x, i.e. are we at the right point ?
                    // first a special case, if we are already at the last element, return it
                    if (index == ItemsSource.Count - 1) {
                        return index;
                    }
                    TimelineInfo tiRight = (TimelineInfo) ItemsSource[index + 1];
                    if (tiRight.CumulatedWidth > x) {
                        // yes, x is between index and (index + 1)
                        return index;
                    }
                    // no, at this index we really are to the left of x => increase the lower limit
                    low = index + 1;
                }
            }

            return low; // or high, they're identical
        }

        private static void OnItemsWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {

            FrameworkElement fe = d as FrameworkElement;
            if (fe != null) {

                FrameworkElement parent = fe.Parent as FrameworkElement;
                if (parent != null) {
                    parent.Width = (long) e.NewValue;
                }
            }

            //App.DebugLog("New items width = {0}", e.NewValue);
        }

        protected override void OnRender(DrawingContext dc) {

            if (ItemsSource == null || ItemsWidth == 0) {
                return;
            }

            Rect drawRectangle = new Rect(0, 0, 0, 20);
            Brush mostCommonBrush = null;

            foreach (TimelineInfo ti in ItemsSource) {

                if (ti.IsMostCommonBrush) {

                    drawRectangle.Width = ItemsWidth - drawRectangle.X;
                    dc.DrawRectangle(ti.Brush, null, drawRectangle);
                    mostCommonBrush = ti.Brush;
                }

                // do not draw the most common brush, it's been done above
                if (! ti.Brush.Equals(mostCommonBrush)) {
                    drawRectangle.Width = ti.Width;
                    dc.DrawRectangle(ti.Brush, null, drawRectangle);
                }
                drawRectangle.X += ti.Width;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged = (sender, args) => { };

        private void OnPropertyChanged(string propertyName) {

            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private void DebugLog(string text, params object[] parameters) {
            App.DebugLog(text, parameters);
        }
    }

    public class TimelineInfo : BaseNotifyableObject {

        public long Width { get; set; }

        public long CumulatedWidth { get; set; }

        public Brush Brush { get; set; }

        /// <summary>
        /// Does this timeline info contain the brush that is most common (highest width) ?
        /// </summary>
        /// <remarks>
        /// There are many entries with this brush, but only one has this property set to true
        /// </remarks>
        public bool IsMostCommonBrush { get; set; }

        public string Explanation { get; set; }

        public DateTime StartTime { get; set; }
    }
}
